package top.swimmer.core.mybatis;

import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.util.Assert;
import top.swimmer.core.dao.WindstormMapper;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static org.springframework.util.Assert.notNull;

public class MagicMapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
    private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();

    private Class<T> mapperInterface;

    private boolean addToConfig = true;

    private SqlSession slaveSqlSession;

    public void setMapperInterface(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public void setAddToConfig(boolean addToConfig) {
        this.addToConfig = addToConfig;
    }

    @Override
    protected void checkDaoConfig() {
        super.checkDaoConfig();

        notNull(this.mapperInterface, "Property 'mapperInterface' is required");

        Configuration configuration = getSqlSession().getConfiguration();
        if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
            try {
                configuration.addMapper(this.mapperInterface);
            } catch (Throwable t) {
                logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", t);
                throw new IllegalArgumentException(t);
            } finally {
                ErrorContext.instance().reset();
            }
        }
    }

    public T getObject() throws Exception {
        Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required");

        Configuration configuration = getSqlSession().getConfiguration();
        if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
            try {
                configuration.addMapper(this.mapperInterface);
            } catch (Throwable t) {
                logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", t);
                throw new IllegalArgumentException(t);
            } finally {
                ErrorContext.instance().reset();
            }
        }

        if (WindstormMapper.class.isAssignableFrom(this.mapperInterface)) {
            return MagicMapperProxy.newMapperProxy(this.mapperInterface,
                    getSqlSession(),
                    getSlaveSqlSession(),
                    methodCache);
        } else {
            return getSqlSession().getMapper(this.mapperInterface);
        }
    }

    public Class<T> getObjectType() {
        return this.mapperInterface;
    }

    public boolean isSingleton() {
        return true;
    }

    public void setSlaveSqlSessionFactory(SqlSessionFactory slaveSqlSessionFactory) {
        this.slaveSqlSession = new SqlSessionTemplate(slaveSqlSessionFactory);
    }

    private SqlSession getSlaveSqlSession() {
        return this.slaveSqlSession;
    }
}
